/*****************************************************************************
*                                                                            *
*  SVG Path Rounding Function                                                *
*  Copyright (C) 2014 Yona Appletree                                         *
*                                                                            *
*  Licensed under the Apache License, Version 2.0 (the "License");           *
*  you may not use this file except in compliance with the License.          *
*  You may obtain a copy of the License at                                   *
*                                                                            *
*      http://www.apache.org/licenses/LICENSE-2.0                            *
*                                                                            *
*  Unless required by applicable law or agreed to in writing, software       *
*  distributed under the License is distributed on an "AS IS" BASIS,         *
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *
*  See the License for the specific language governing permissions and       *
*  limitations under the License.                                            *
*                                                                            *
*****************************************************************************/

/**
 * SVG Path rounding function. Takes an input path string and outputs a path
 * string where all line-line corners have been rounded. Only supports absolute
 * commands at the moment.
 * 
 * @param pathString The SVG input path
 * @param radius The amount to round the corners, either a value in the SVG 
 *               coordinate space, or, if useFractionalRadius is true, a value
 *               from 0 to 1.
 * @param useFractionalRadius If true, the curve radius is expressed as a
 *               fraction of the distance between the point being curved and
 *               the previous and next points.
 * @returns A new SVG path string with the rounding
 */
export default function roundPathCorners(pathString, radius, useFractionalRadius) {
    function moveTowardsLength(movingPoint, targetPoint, amount) {
      let width = (targetPoint.x - movingPoint.x);
      let height = (targetPoint.y - movingPoint.y);
      
      let distance = Math.sqrt(width*width + height*height);
      
      return moveTowardsFractional(movingPoint, targetPoint, Math.min(1, amount / distance));
    }
    function moveTowardsFractional(movingPoint, targetPoint, fraction) {
      return {
        x: movingPoint.x + (targetPoint.x - movingPoint.x)*fraction,
        y: movingPoint.y + (targetPoint.y - movingPoint.y)*fraction
      };
    }
    
    // Adjusts the ending position of a command
    function adjustCommand(cmd, newPoint) {
      if (cmd.length > 2) {
        cmd[cmd.length - 2] = newPoint.x;
        cmd[cmd.length - 1] = newPoint.y;
      }
    }
    
    // Gives an {x, y} object for a command's ending position
    function pointForCommand(cmd) {
      return {
        x: parseFloat(cmd[cmd.length - 2]),
        y: parseFloat(cmd[cmd.length - 1])
      };
    }
    
    // Split apart the path, handing concatonated letters and numbers
    let pathParts = pathString
      .split(/[,\s]/)
      .reduce(function(parts, part){
        let match = part.match('([a-zA-Z])(.+)');
        if (match) {
          parts.push(match[1]);
          parts.push(match[2]);
        } else {
          parts.push(part);
        }
        
        return parts;
      }, []);
    
    // Group the commands with their arguments for easier handling
    let commands = pathParts.reduce(function(commands, part) {
      if (parseFloat(part) == part && commands.length) {
        commands[commands.length - 1].push(part);
      } else {
        commands.push([part]);
      }
      
      return commands;
    }, []);
    
    // The resulting commands, also grouped
    let resultCommands = [];
    
    if (commands.length > 1) {
      let startPoint = pointForCommand(commands[0]);
      
      // Handle the close path case with a "virtual" closing line
      let virtualCloseLine = null;
      if (commands[commands.length - 1][0] == 'Z' && commands[0].length > 2) {
        virtualCloseLine = ['L', startPoint.x, startPoint.y];
        commands[commands.length - 1] = virtualCloseLine;
      }
      
      // We always use the first command (but it may be mutated)
      resultCommands.push(commands[0]);
      
      for (let cmdIndex=1; cmdIndex < commands.length; cmdIndex++) {
        let prevCmd = resultCommands[resultCommands.length - 1];
        
        let curCmd = commands[cmdIndex];
        
        // Handle closing case
        let nextCmd = (curCmd == virtualCloseLine)
          ? commands[1]
          : commands[cmdIndex + 1];
        
        // Nasty logic to decide if this path is a candidite.
        if (nextCmd && prevCmd && (prevCmd.length > 2) && curCmd[0] == 'L' && nextCmd.length > 2 && nextCmd[0] == 'L') {
          // Calc the points we're dealing with
          let prevPoint = pointForCommand(prevCmd);
          let curPoint = pointForCommand(curCmd);
          let nextPoint = pointForCommand(nextCmd);
          
          // The start and end of the cuve are just our point moved towards the previous and next points, respectivly
          let curveStart, curveEnd;
          
          if (useFractionalRadius) {
            curveStart = moveTowardsFractional(curPoint, prevCmd.origPoint || prevPoint, radius);
            curveEnd = moveTowardsFractional(curPoint, nextCmd.origPoint || nextPoint, radius);
          } else {
            curveStart = moveTowardsLength(curPoint, prevPoint, radius);
            curveEnd = moveTowardsLength(curPoint, nextPoint, radius);
          }
          
          // Adjust the current command and add it
          adjustCommand(curCmd, curveStart);
          curCmd.origPoint = curPoint;
          resultCommands.push(curCmd);
          
          // The curve control points are halfway between the start/end of the curve and
          // the original point
          let startControl = moveTowardsFractional(curveStart, curPoint, .5);
          let endControl = moveTowardsFractional(curPoint, curveEnd, .5);
    
          // Create the curve 
          let curveCmd = ['C', startControl.x, startControl.y, endControl.x, endControl.y, curveEnd.x, curveEnd.y];
          // Save the original point for fractional calculations
          curveCmd.origPoint = curPoint;
          resultCommands.push(curveCmd);
        } else {
          // Pass through commands that don't qualify
          resultCommands.push(curCmd);
        }
      }
      
      // Fix up the starting point and restore the close path if the path was orignally closed
      if (virtualCloseLine) {
        let newStartPoint = pointForCommand(resultCommands[resultCommands.length-1]);
        resultCommands.push(['Z']);
        adjustCommand(resultCommands[0], newStartPoint);
      }
    } else {
      resultCommands = commands;
    }
    
    return resultCommands.reduce(function(str, c){ return str + c.join(' ') + ' '; }, '');
  }
